package ar.edu.itba.it.cg.grupo01.impl.scene.optimization;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;

import java.util.ArrayList;
import java.util.Collection;

import javax.vecmath.Point3f;

import ar.edu.itba.it.cg.grupo01.api.Intersectable;
import ar.edu.itba.it.cg.grupo01.api.Intersection;
import ar.edu.itba.it.cg.grupo01.api.Ray;
import ar.edu.itba.it.cg.grupo01.api.shape.Shape;
import ar.edu.itba.it.cg.grupo01.impl.IntersectionFactory;
import ar.edu.itba.it.cg.grupo01.impl.shape.AABB;
import ar.edu.itba.it.cg.grupo01.impl.shape.TriangleMesh;

public class Octree implements Intersectable {

	private int shapesPerNode;
	private OctNode root;

	public Octree(Collection<? extends Shape> shapes,
			IntersectionFactory intersectionFactory) {
		this(shapes, intersectionFactory, 7, 10);
	}

	public Octree(Collection<? extends Shape> shapes,
			IntersectionFactory intersectionFactory, int shapesPerNode,
			int maxDepth) {
		checkArgument(shapesPerNode > 0,
				"shapesPerNode should be greater than 0");
		checkArgument(maxDepth > 0, "maxDepth should be greater than 0");
		checkNotNull(shapes, "Scene should not be null");
		checkNotNull(intersectionFactory,
				"IntersectionFactory should not be null");

		AABB box = buildBox(shapes, intersectionFactory);
		
		Collection<Shape> octreeShapes = new ArrayList<Shape>();
		for (Shape shape : shapes) {
			if( shape.getShapeCount() == 1 ) {
				octreeShapes.add(shape);
			}
			else if (shape instanceof TriangleMesh) {
				TriangleMesh tm = (TriangleMesh) shape;
				octreeShapes.addAll(tm.getTriangles());
			}
		}
		
		this.root = OctNode.of(octreeShapes, box, shapesPerNode, maxDepth - 1,
				intersectionFactory);
		this.shapesPerNode = shapesPerNode;
	}

	private AABB buildBox(Collection<? extends Shape> shapes,
			IntersectionFactory intersectionFactory) {

		float xmin, ymin, zmin;
		float xmax, ymax, zmax;

		xmin = ymin = zmin = Float.MAX_VALUE;
		xmax = ymax = zmax = Float.MIN_VALUE;

		for (Shape shape : shapes) {

			Point3f min = shape.getMinXYZ();
			Point3f max = shape.getMaxXYZ();

			xmin = Math.min(xmin, min.x);
			ymin = Math.min(ymin, min.y);
			zmin = Math.min(zmin, min.z);

			xmax = Math.max(xmax, max.x);
			ymax = Math.max(ymax, max.y);
			zmax = Math.max(zmax, max.z);
		}

		return new AABB(new Point3f(xmin, ymin, zmin), new Point3f(xmax, ymax,
				zmax), intersectionFactory);
	}

	@Override
	public Intersection<? extends Shape> intersects(Ray ray) {
		return this.root.intersects(ray);
	}

	public int getDepth() {
		return this.root.getDepth();
	}

	public int leafAmount() {
		return this.root.leafAmount();
	}

	public Intersection<? extends Shape> hitsLight(Ray lightRay, Shape shape) {
		return this.root.hitsLight(lightRay,shape);
	}
}
